package cn.com.thinker.security.sdk.sso.filter;

import cn.com.thinker.security.sdk.constant.WebConstants;
import cn.com.thinker.security.sdk.sso.authentication.EncryCredential;
import cn.com.thinker.security.sdk.sso.authentication.EncryCredentialManagerImpl;
import cn.com.thinker.security.sdk.sso.handler.ClientLoginHandler;
import cn.com.thinker.security.sdk.sso.key.KeyService;
import cn.com.thinker.security.sdk.sso.key.Thin4soKey;
import cn.com.thinker.security.sdk.sso.key.impl.DefaultKeyServiceImpl;
import cn.com.thinker.security.sdk.sso.model.EncryCredentialInfo;
import cn.com.thinker.security.sdk.tools.SsoUtils;
import cn.com.thinker.tools.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * thin4so客户端应用的过滤器，从而实现集成thin4so单点登录系统。
 * 此过滤器必须安装或者自己实现。
 *
 * @author Administrator
 */

public class Thin4soClientFilter extends BaseClientFilter  {

    private static Logger logger = Logger.getLogger(Thin4soClientFilter.class.getName());

    /**
     * 在客户端的session中的用户信息，避免频繁认证，提高性能。
     */
    public static final String USER_STATE_IN_SESSION_KEY = "thin4so_client_user_info_session_key";


    /**
     * thin4so服务器登录URL地址。
     */
    protected String thin4soServerLoginUrl = thin4soServerHost + "login.do";

    /**
     * thin4so服务器获取应用秘钥信息的URL地址。
     */
    protected String thin4soServerFetchKeyUrl = thin4soServerHost + "fetchKey.do";

    /**
     * 本应用在thin4so服务器上的应用ID值。
     */
    protected String thin4soClientAppId = "1001";

    /**
     * 登录本应用处理器类，由此类进行构造一个对象。
     */
    protected String appClientLoginHandlerClass = "cn.com.thinker.security.sdk.sso.handler.impl.DefaultClientLoginHandlerImpl";


    /**
     * 本应用对应的加密key.
     */
    protected Thin4soKey thin4soKey;

    /**
     * 秘钥获取服务。
     */
    protected KeyService keyService = null;

    /**
     * 凭据管理器。
     */
    protected EncryCredentialManagerImpl encryCredentialManager;

    /**
     * 登录本应用的处理器。
     */
    protected ClientLoginHandler appClientLoginHandler;


    @Override
    public void doInit(FilterConfig filterConfig) throws ServletException {
        thin4soClientAppId = getInitParameterWithDefalutValue(filterConfig, "thin4soClientAppId", thin4soClientAppId);
        thin4soServerLoginUrl = getInitParameterWithDefalutValue(filterConfig, "thin4soServerLoginUrl", thin4soServerHost + "login.do");
        ;
        thin4soServerFetchKeyUrl = getInitParameterWithDefalutValue(filterConfig, "thin4soServerFetchKeyUrl", thin4soServerHost + "fetchKey.do");
        appClientLoginHandlerClass = getInitParameterWithDefalutValue(filterConfig, "appClientLoginHandlerClass", appClientLoginHandlerClass);
        //构造key服务等相关对象。
        //构造登录本应用的处理器对象。
        if (!StringUtils.isEmpty(appClientLoginHandlerClass)) {
            try {
                this.appClientLoginHandler = (ClientLoginHandler) Class.forName(appClientLoginHandlerClass).newInstance();
            } catch (Exception e) {
                //  handle exception
                logger.log(Level.SEVERE,ExceptionUtils.getStackTraceAsString(e));
            }
        }
        keyService = new DefaultKeyServiceImpl(thin4soServerFetchKeyUrl, thin4soClientAppId);
        this.encryCredentialManager = new EncryCredentialManagerImpl();
        this.encryCredentialManager.setKeyService(keyService);
        logger.info("the thin4so sever is :" + this.thin4soServerHost + ", please check this service is ok.");
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpSession session = servletRequest.getSession();
        try {
            //本地应用未登录。
            if (session.getAttribute(USER_STATE_IN_SESSION_KEY) == null) {
                //查找参数中是否存在thin4so_client_ec值，若没有则重定向到登录页面。
                String thin4so_client_ec = getClientEC(servletRequest);
                if (StringUtils.isEmpty(thin4so_client_ec)) {
                    // 交给shiro，shiro认证后，重定向到访问页面
                    session.setAttribute(WebUtils.SAVED_REQUEST_KEY, new SavedRequest(servletRequest));
                    //跳转到Thin4so登录页面。
                    servletResponse.sendRedirect(buildRedirectToThin4soServer(servletRequest));
                    return;
                }
                //如果没有key，则重试获取一次。
                if (thin4soKey == null) {
                    try {
                        thin4soKey = keyService.findKeyByAppId(thin4soClientAppId);
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, "fetch thin4so key info error", e);
                    }
                }
                //解密凭据信息。
                EncryCredentialInfo encryCredentialInfo = this.encryCredentialManager.decrypt(new EncryCredential(thin4so_client_ec));
                if (encryCredentialInfo != null) {
                    //检查凭据合法性。
                    boolean valid = this.encryCredentialManager.checkEncryCredentialInfo(encryCredentialInfo);
                    //如果合法，则继续其它处理。
                    if (valid) {
                        //设置登录状态到session中。
                        session.setAttribute(USER_STATE_IN_SESSION_KEY, encryCredentialInfo);
                        //触发登录本应用的处理。
                        if (appClientLoginHandler != null) {
                            //登录本应用。
                            appClientLoginHandler.loginClient(encryCredentialInfo, servletRequest, servletResponse);
                        }

                        //重新定位到原请求，去除EC参数。
                        String url = servletRequest.getRequestURL().toString();
                        if (!StringUtils.isEmpty(url)) {
                            //如果请求中存在EC参数，则去除这个参数，重定位。
                            if (url.contains(WebConstants.THIN4SO_CLIENT_ENCRYPTED_CREDENTIAL_COOKIE_KEY)) {
                                url = url.substring(0, url.indexOf(WebConstants.THIN4SO_CLIENT_ENCRYPTED_CREDENTIAL_COOKIE_KEY));
                                //去除末尾的问号。
                                if (url.endsWith("?")) {
                                    url = url.substring(0, url.length() - 1);
                                }

                                //去除末尾的&符号。
                                if (url.endsWith("&")) {
                                    url = url.substring(0, url.length() - 1);
                                }
                            }
                        }

                        //登录成功后，写入EC到cookie中。 不保存cookie，使用分布式session处理session同步问题。
						writeEC(thin4so_client_ec, servletRequest,servletResponse);

                        //重新定位请求，避免尾部出现长参数。
                        servletResponse.sendRedirect(url);
                        return;
                    }
                }
                //否则凭据信息不合法，跳转到Thin4so登录页面。
                servletResponse.sendRedirect(buildRedirectToThin4soServer(servletRequest));
                return;
            }

            if(chain!=null) {
                //若已经登录过，则直接返回，继续其它过滤器。
                chain.doFilter(request, response);
            }
        }
        //处理异常信息。
        catch (Exception e) {
            logger.log(Level.SEVERE, ExceptionUtils.getStackTraceAsString(e));
            removeCookeEC(servletRequest, servletResponse);
            //否则凭据信息不合法，跳转到Thin4so登录页面。
            servletResponse.sendRedirect(buildRedirectToThin4soServer(servletRequest));
        }

    }

    protected String buildRedirectToThin4soServer(HttpServletRequest servletRequest) {

        thin4soServerLoginUrl = SsoUtils.chooseUrl(thin4soServerLoginUrl);

        StringBuffer sb = new StringBuffer(this.thin4soServerLoginUrl);
        if (this.thin4soServerLoginUrl.contains("?")) {
            sb.append("&");
        } else {
            sb.append("?");
        }
        sb.append("service=").append(servletRequest.getRequestURL().toString());
        return sb.toString();
    }

    @Override
    public void destroy() {
        this.thin4soKey = null;
    }


    /**
     * 从客户端参数或者cookie中获取EC值。
     *
     * @param request http请求对象。
     * @return EC值。
     */
    protected String getClientEC(HttpServletRequest request) {
        String ec = null;
        if (request != null) {
            ec = request.getParameter(WebConstants.THIN4SO_CLIENT_ENCRYPTED_CREDENTIAL_COOKIE_KEY);
            // 再从session中获取
            if (StringUtils.isEmpty(ec)) {
                Object attribute = request.getSession().getAttribute(WebConstants.THIN4SO_CLIENT_ENCRYPTED_CREDENTIAL_COOKIE_KEY);
                ec = attribute == null? null:attribute.toString();
            }


        }
        return ec;
    }

    /**
     * 将EC的值写入到服务器的cookie中。
     *
     * @param ec       EC值。
     * @param response Http响应对象。
     */
    protected void writeEC(String ec, HttpServletRequest request,HttpServletResponse response) {
        // 保存到session
        try {
            request.getSession().setAttribute(WebConstants.THIN4SO_CLIENT_ENCRYPTED_CREDENTIAL_COOKIE_KEY, ec);
            return;
        }catch (Exception e){
            logger.log(Level.SEVERE, "writeEC to session error", e);
        }

    }

}
